// 版权所有2011 Go作者。版权所有。
// 此源代码的使用受BSD样式的约束
// 可以在许可证文件中找到的许可证。
// None
// 内联设施进行2次传递：第一个caninl确定
// 函数适用于内联，也适用于那些内联的函数
// 保存正文的副本。然后InlineCalls将每个函数体带到
// 展开对内联函数的调用。
// None
// l标志控制攻击性。注意main（）交换级别0和1，
// 将1设为默认值，并禁用-l。额外的级别（超过-l）可能是错误的和错误的
// 不支持。
// 0:禁用
// 1:80节点叶函数、OneLiner、死机、惰性类型检查（默认）
// 2：（未分配）
// 3：（未分配）
// 4：允许非叶函数
// None
// 在某些情况下，这可能会得到另一个默认值，并成为带-N的关闭选项。
// None
// -d typcheckinl标志允许对所有导入的实体进行早期类型检查，
// 这对清除bug很有用。
// None
// Debug.m标志启用诊断输出。单个-m用于验证
// 哪些调用是内联的，哪些调用不是内联的，更多的是为了调试，并且可能会在任何时候消失。

package inline

import (
	"fmt"
	"go/constant"
	"strings"

	"cmd/compile/internal/base"
	"cmd/compile/internal/ir"
	"cmd/compile/internal/logopt"
	"cmd/compile/internal/typecheck"
	"cmd/compile/internal/types"
	"cmd/internal/obj"
	"cmd/internal/src"
)

// 内联预算参数，集中在一个地方
const (
	inlineMaxBudget       = 80
	inlineExtraAppendCost = 0
	// 如果最多有一个调用，则默认为内联-l=4通过使用1替代此选项。
	inlineExtraCallCost  = 57              // 57被设定为基准，以提供最大的效益，而没有任何不好的意外；请参阅https:
	inlineExtraPanicCost = 1               // 不要惩罚内联恐慌。
	inlineExtraThrowCost = inlineMaxBudget // 对于当前（2018-05/1.11）代码，内联runtime.throw没有帮助。

	inlineBigFunctionNodes   = 5000 // 有这么多节点的函数被认为是“大的”。
	inlineBigFunctionMaxCost = 20   // 内联到“大”函数时内联的最大成本。
)

// InlinePackage查找可以内联的函数，并在walk展开它们之前对它们进行克隆。
func InlinePackage() {
	ir.VisitFuncsBottomUp(typecheck.Target.Decls, func(list []*ir.Func, recursive bool) {
		numfns := numNonClosures(list)
		for _, n := range list {
			if !recursive || numfns > 1 {
				// 如果没有内联，我们允许内联
				// 递归，或者递归周期是
				// 跨越多个函数。
				CanInline(n)
			} else {
				if base.Flag.LowerM > 1 {
					fmt.Printf("%v: cannot inline %v: recursive\n", ir.Line(n), n.Nname)
				}
			}
			InlineCalls(n)
		}
	})
}

// CanInline确定fn是否可内联。
// 如果是，则CanInline将fn.Body和fn.Dcl的副本保存在fn.Inl中。
// fn和fn.Body将已经过类型检查。
func CanInline(fn *ir.Func) {
	if fn.Nname == nil {
		base.Fatalf("CanInline no nname %+v", fn)
	}

	var reason string // 函数未内联的原因（如果有）
	if base.Flag.LowerM > 1 || logopt.Enabled() {
		defer func() {
			if reason != "" {
				if base.Flag.LowerM > 1 {
					fmt.Printf("%v: cannot inline %v: %s\n", ir.Line(fn), fn.Nname, reason)
				}
				if logopt.Enabled() {
					logopt.LogOpt(fn.Pos(), "cannotInlineFunction", "inline", ir.FuncName(fn), reason)
				}
			}
		}()
	}

	// 如果标记为“go:noinline”，则不要内联
	if fn.Pragma&ir.Noinline != 0 {
		reason = "marked go:noinline"
		return
	}

	// 如果标记为“go:norace”和-race编译，则不要内联。
	if base.Flag.Race && fn.Pragma&ir.Norace != 0 {
		reason = "marked go:norace with -race compilation"
		return
	}

	// 如果标记为“go:nocheckptr”和-d checkptr编译，则不要内联。
	if base.Debug.Checkptr != 0 && fn.Pragma&ir.NoCheckPtr != 0 {
		reason = "marked go:nocheckptr"
		return
	}

	// 如果标记为“go:cgo\u unsafe\u args”，则不要内联，因为
	// 函数对其参数框架布局进行假设。
	if fn.Pragma&ir.CgoUnsafeArgs != 0 {
		reason = "marked go:cgo_unsafe_args"
		return
	}

	// 如果标记为“go:uintpttrescapes”，则不要内联，因为
	// 内联期间，转义信息丢失。
	if fn.Pragma&ir.UintptrEscapes != 0 {
		reason = "marked as having an escaping uintptr argument"
		return
	}

	// nowritebarrierrec检查器当前在功能上工作
	// 粒度，因此内联yeswritebarrierrec函数可以
	// 混淆它（#22342）。作为一种解决方法，不允许内联
	// 暂时不要。
	if fn.Pragma&ir.Yeswritebarrierrec != 0 {
		reason = "marked go:yeswritebarrierrec"
		return
	}

	// 如果fn没有正文（在Go之外定义），则无法将其内联。
	if len(fn.Body) == 0 {
		reason = "no function body"
		return
	}

	if fn.Typecheck() == 0 {
		base.Fatalf("CanInline on non-typechecked function %v", fn)
	}

	n := fn.Nname
	if n.Func.InlinabilityChecked() {
		return
	}
	defer n.Func.SetInlinabilityChecked(true)

	cc := int32(inlineExtraCallCost)
	if base.Flag.LowerL == 4 {
		cc = 1 // 这似乎比0产生更好的性能。
	}

	// 在这一点上，在游戏中的功能，我们正在寻找可能
	// 有“过时”的汽车，仍然出现在Dcl列表中的VAR，但是
	// 在函数体中不再有任何用途（由于
	// 通过死码消除）。我们想排除这些死去的士兵
	// 创建下面的“Inline.Dcl”字段时；为此,，
	// 下面的hairyVisitor构建了一个已使用/引用的地图
	// 我们使用这个映射生成一个经过修剪的Inline.Dcl
	// 列表更多内容见第25249期。

	visitor := hairyVisitor{
		budget:        inlineMaxBudget,
		extraCallCost: cc,
	}
	if visitor.tooHairy(fn) {
		reason = visitor.reason
		return
	}

	n.Func.Inl = &ir.Inline{
		Cost: inlineMaxBudget - visitor.budget,
		Dcl:  pruneUnusedAutos(n.Defn.(*ir.Func).Dcl, &visitor),
		Body: inlcopylist(fn.Body),
	}

	if base.Flag.LowerM > 1 {
		fmt.Printf("%v: can inline %v with cost %d as: %v { %v }\n", ir.Line(fn), n, inlineMaxBudget-visitor.budget, fn.Type(), ir.Nodes(n.Func.Inl.Body))
	} else if base.Flag.LowerM != 0 {
		fmt.Printf("%v: can inline %v\n", ir.Line(fn), n)
	}
	if logopt.Enabled() {
		logopt.LogOpt(fn.Pos(), "canInlineFunction", "inline", ir.FuncName(fn), fmt.Sprintf("cost: %d", inlineMaxBudget-visitor.budget))
	}
}

// Inline_Flood将n的内联体标记为导出，并递归地确保
// 所有被调用的函数也被标记。
func Inline_Flood(n *ir.Name, exportsym func(*ir.Name)) {
	if n == nil {
		return
	}
	if n.Op() != ir.ONAME || n.Class != ir.PFUNC {
		base.Fatalf("Inline_Flood: unexpected %v, %v, %v", n, n.Op(), n.Class)
	}
	fn := n.Func
	if fn == nil {
		base.Fatalf("Inline_Flood: missing Func on %v", n)
	}
	if fn.Inl == nil {
		return
	}

	if fn.ExportInline() {
		return
	}
	fn.SetExportInline(true)

	typecheck.ImportedBody(fn)

	var doFlood func(n ir.Node)
	doFlood = func(n ir.Node) {
		switch n.Op() {
		case ir.OMETHEXPR, ir.ODOTMETH:
			Inline_Flood(ir.MethodExprName(n), exportsym)

		case ir.ONAME:
			n := n.(*ir.Name)
			switch n.Class {
			case ir.PFUNC:
				Inline_Flood(n, exportsym)
				exportsym(n)
			case ir.PEXTERN:
				exportsym(n)
			}

		case ir.OCALLPART:
			// 好吧，因为我们还不知道
			// 调用方法值。
		case ir.OCLOSURE:
			// VisitList不访问闭包实体，所以强制
			// 对闭包主体上的VisitList的递归调用。
			ir.VisitList(n.(*ir.ClosureExpr).Func.Body, doFlood)
		}
	}

	// 递归地标识的所有引用函数
	// 再出口。我们甚至想包括未调用的函数，
	// 因为在内联之后，它们可能是可调用的。
	ir.VisitList(ir.Nodes(fn.Inl.Body), doFlood)
}

// hairyVisitor访问函数体以确定其内联
// 毛羽和是否可以内联。
type hairyVisitor struct {
	budget        int32
	reason        string
	extraCallCost int32
	usedLocals    ir.NameSet
	do            func(ir.Node) bool
}

func (v *hairyVisitor) tooHairy(fn *ir.Func) bool {
	v.do = v.doNode // 缓存关闭
	if ir.DoChildren(fn, v.do) {
		return true
	}
	if v.budget < 0 {
		v.reason = fmt.Sprintf("function too complex: cost %d exceeds budget %d", inlineMaxBudget-v.budget, inlineMaxBudget)
		return true
	}
	return false
}

func (v *hairyVisitor) doNode(n ir.Node) bool {
	if n == nil {
		return false
	}
	switch n.Op() {
	// 如果可以通话，而且我们有身体的预算，通话就可以了。
	case ir.OCALLFUNC:
		n := n.(*ir.CallExpr)
		// 调用runtime.getcaller{pc，sp}的函数不能内联
		// 因为getcaller{pc，sp}需要指向调用方第一个参数的指针。
		// None
		// throw是一个“廉价调用”，就像普通代码中的panic一样。
		if n.X.Op() == ir.ONAME {
			name := n.X.(*ir.Name)
			if name.Class == ir.PFUNC && types.IsRuntimePkg(name.Sym().Pkg) {
				fn := name.Sym().Name
				if fn == "getcallerpc" || fn == "getcallersp" {
					v.reason = "call to " + fn
					return true
				}
				if fn == "throw" {
					v.budget -= inlineExtraThrowCost
					break
				}
			}
		}

		if ir.IsIntrinsicCall(n) {
			// 像对待任何其他节点一样对待。
			break
		}

		if fn := inlCallee(n.X); fn != nil && fn.Inl != nil {
			v.budget -= fn.Inl.Cost
			break
		}

		// 非叶内联的调用成本。
		v.budget -= v.extraCallCost

	// 如果可以通话，而且我们有身体的预算，通话就可以了。
	case ir.OCALLMETH:
		n := n.(*ir.CallExpr)
		t := n.X.Type()
		if t == nil {
			base.Fatalf("no function type for [%p] %+v\n", n.X, n.X)
		}
		fn := ir.MethodExprName(n.X).Func
		if types.IsRuntimePkg(fn.Sym().Pkg) && fn.Sym().Name == "heapBits.nextArena" {
			// 特殊情况：显式允许
			// 烟囱中内衬
			// runtime.heapBits.next即使
			// 它叫慢路
			// runtime.heapBits.nextArena。
			break
		}
		if fn.Inl != nil {
			v.budget -= fn.Inl.Cost
			break
		}
		// 非叶内联的调用成本。
		v.budget -= v.extraCallCost

	// 不管预算如何，都是些太麻烦的事情
	case ir.OCALL, ir.OCALLINTER:
		// 非叶内联的调用成本。
		v.budget -= v.extraCallCost

	case ir.OPANIC:
		n := n.(*ir.UnaryExpr)
		if n.X.Op() == ir.OCONVIFACE && n.X.(*ir.ConvExpr).Implicit() {
			// Hack使reflect.flag.testintendeinling必须是可内联的。
			// 在CL 284412之前，这些转换在
			// 编译器，所以它们不计入内联预算。
			v.budget++
		}
		v.budget -= inlineExtraPanicCost

	case ir.ORECOVER:
		// recover与要查找的参数帧指针匹配
		// 正确的恐慌值，因此它需要一个参数框架。
		v.reason = "call to recover"
		return true

	case ir.OCLOSURE:
		if base.Debug.InlFuncsWithClosures == 0 {
			v.reason = "not inlining functions with closures"
			return true
		}

		// TODO（danscales）：可能使预算与关闭次数成比例
		// 变量，例如：
		// v、 预算-=int32（长度（n.（*ir.ClosureExpr）.Func.ClosureVars）*3）
		v.budget -= 15
		// 扫描封闭体（儿童不会自动扫描）
		// do）检查车身中是否存在不允许的ops，包括
		// 预算案的主体。
		if doList(n.(*ir.ClosureExpr).Func.Body, v.do) {
			return true
		}

	case ir.ORANGE,
		ir.OSELECT,
		ir.OGO,
		ir.ODEFER,
		ir.ODCLTYPE, // 还不能打印
		ir.OTAILCALL:
		v.reason = "unhandled op " + n.Op().String()
		return true

	case ir.OAPPEND:
		v.budget -= inlineExtraAppendCost

	case ir.ODEREF:
		// *（*X）（不安全的指针（&X））是低成本的
		n := n.(*ir.StarExpr)

		ptr := n.X
		for ptr.Op() == ir.OCONVNOP {
			ptr = ptr.(*ir.ConvExpr).X
		}
		if ptr.Op() == ir.OADDR {
			v.budget += 1 // 撤消ir.ODEREF+ir.OADDR默认成本的一半
		}

	case ir.OCONVNOP:
		// 这不会产生代码，但孩子们可能会。
		v.budget++ // 撤消默认成本

	case ir.ODCLCONST, ir.OFALL:
		// 这些节点不生成代码；从内联预算中省略。
		return false

	case ir.OFOR, ir.OFORUNTIL:
		n := n.(*ir.ForStmt)
		if n.Label != nil {
			v.reason = "labeled control"
			return true
		}
	case ir.OSWITCH:
		n := n.(*ir.SwitchStmt)
		if n.Label != nil {
			v.reason = "labeled control"
			return true
		}
	// 案例ir.ORANGE，ir.O在上面的“未处理”中选择

	case ir.OBREAK, ir.OCONTINUE:
		n := n.(*ir.BranchStmt)
		if n.Label != nil {
			// 应因上述标记的控制错误而短路。
			base.Fatalf("unexpected labeled break/continue: %v", n)
		}

	case ir.OIF:
		n := n.(*ir.IfStmt)
		if ir.IsConst(n.Cond, constant.Bool) {
			// 如果这样做，条件就不需要任何费用。
			// 托多（rsc）：我们去拜访死树枝似乎很奇怪。
			return doList(n.Init(), v.do) ||
				doList(n.Body, v.do) ||
				doList(n.Else, v.do)
		}

	case ir.ONAME:
		n := n.(*ir.Name)
		if n.Class == ir.PAUTO {
			v.usedLocals.Add(n)
		}

	case ir.OBLOCK:
		// 此时，我们应该看到的唯一对象是一个空对象。
		// 无论如何，让下面的visitList（n.List（））处理这些语句，
		// 而且不要为对象本身收费。++撤销下面的--。
		v.budget++

	case ir.OCALLPART, ir.OSLICELIT:
		v.budget-- // 黑客工具库-cmp。

	case ir.OMETHEXPR:
		v.budget++ // 黑客工具库-cmp。
	}

	v.budget--

	// 调试时，不要过早停止，以获得内联此函数的全部成本
	if v.budget < 0 && base.Flag.LowerM < 2 && !logopt.Enabled() {
		v.reason = "too expensive"
		return true
	}

	return ir.DoChildren(n, v.do)
}

func isBigFunc(fn *ir.Func) bool {
	budget := inlineBigFunctionNodes
	return ir.Any(fn, func(n ir.Node) bool {
		budget--
		return budget <= 0
	})
}

// inlcopylist（与inlcopy一起）递归复制节点列表，但
// 它保持相同的ONAME、OTYPE和OLITERAL节点。它是用来复制的
// 内联函数的主体和DCL。
func inlcopylist(ll []ir.Node) []ir.Node {
	s := make([]ir.Node, len(ll))
	for i, n := range ll {
		s[i] = inlcopy(n)
	}
	return s
}

// inlcopy类似于DeepCopy（），但需要额外的工作来复制闭包。
func inlcopy(n ir.Node) ir.Node {
	var edit func(ir.Node) ir.Node
	edit = func(x ir.Node) ir.Node {
		switch x.Op() {
		case ir.ONAME, ir.OTYPE, ir.OLITERAL, ir.ONIL:
			return x
		}
		m := ir.Copy(x)
		ir.EditChildren(m, edit)
		if x.Op() == ir.OCLOSURE {
			x := x.(*ir.ClosureExpr)
			// 需要保存/复制x.Func.Nname，
			// x、 Func.Nname.Ntype、x.Func.Dcl、x.Func.ClosureVars和
			// x、 用于iexport和本地内联的函数体。
			oldfn := x.Func
			newfn := ir.NewFunc(oldfn.Pos())
			if oldfn.ClosureCalled() {
				newfn.SetClosureCalled(true)
			}
			m.(*ir.ClosureExpr).Func = newfn
			newfn.Nname = ir.NewNameAt(oldfn.Nname.Pos(), oldfn.Nname.Sym())
			// XXX是否可以共享fn.Type（）？？
			newfn.Nname.SetType(oldfn.Nname.Type())
			// 对于-G=3模式，Ntype可以为零。
			if oldfn.Nname.Ntype != nil {
				newfn.Nname.Ntype = inlcopy(oldfn.Nname.Ntype).(ir.Ntype)
			}
			newfn.Body = inlcopylist(oldfn.Body)
			// 制作Dcl和ClosureVar切片的浅拷贝
			newfn.Dcl = append([]*ir.Name(nil), oldfn.Dcl...)
			newfn.ClosureVars = append([]*ir.Name(nil), oldfn.ClosureVars...)
		}
		return m
	}
	return edit(n)
}

// InlineCalls/inlnode遍历fn的语句和表达式，并替换任何
// 对内联函数的调用。这是外部入口点。
func InlineCalls(fn *ir.Func) {
	savefn := ir.CurFunc
	ir.CurFunc = fn
	maxCost := int32(inlineMaxBudget)
	if isBigFunc(fn) {
		maxCost = inlineBigFunctionMaxCost
	}
	// 映射以跟踪在特定位置内联的函数
	// 调用site，以便在到达
	// 再次执行递归循环。我们不直接内联递归函数，
	// 但如果有许多函数的递归循环，则允许内联。
	// 最有可能的是，内联将在我们甚至还没有开始时就停止了
	// 这个循环再次出现，但地图捕捉到了不寻常的情况。
	inlMap := make(map[*ir.Func]bool)
	var edit func(ir.Node) ir.Node
	edit = func(n ir.Node) ir.Node {
		return inlnode(n, maxCost, inlMap, edit)
	}
	ir.EditChildren(fn, edit)
	ir.CurFunc = savefn
}

// 将OINLCALL转换为语句。
func inlconv2stmt(inlcall *ir.InlinedCallExpr) ir.Node {
	n := ir.NewBlockStmt(inlcall.Pos(), nil)
	n.List = inlcall.Init()
	n.List.Append(inlcall.Body.Take()...)
	return n
}

// 将OINLCALL转换为单值表达式。
// inlconv2expr的结果必须分配回n，例如。
// n、 左=inlconv2expr（n.左）
func inlconv2expr(n *ir.InlinedCallExpr) ir.Node {
	r := n.ReturnVars[0]
	return ir.InitExpr(append(n.Init(), n.Body...), r)
}

// 在中打开OINLCALL的rlist（带返回值）
// n到一个表达式列表中，将ninit和body集合在一起
// 包含第一个列表元素上的内联语句
// 秩序将得到维护。用于返回、oas2func和call
// 声明。
func inlconv2list(n *ir.InlinedCallExpr) []ir.Node {
	if n.Op() != ir.OINLCALL || len(n.ReturnVars) == 0 {
		base.Fatalf("inlconv2list %+v\n", n)
	}

	s := n.ReturnVars
	s[0] = ir.InitExpr(append(n.Init(), n.Body...), s[0])
	return s
}

// inlnode在树上递归以查找可内联调用，这将
// 通过mkinlcall转换为OINLCALLs。当递归出现时
// 备份将检查左、右、列表、rlist、ninit、ntest、nincr、，
// nbody和nelse并使用上述4个inlconv/glue功能之一
// 将OINLCALL转换为表达式、语句或修补程序
// 在该节点列表或rlist中，视情况而定。
// 请注意，将粘合功能向下传递是没有意义的
// 递归到创建OINLCALL的级别，因为它们
// 必须编辑/this/n，因此您也必须向下推该选项，
// 但是你也可以在这里做。所以这更干净，更安全
// 更短，更简单。
// inlnode的结果必须分配回n，例如。
// n、 左=inlnode（n.左）
func inlnode(n ir.Node, maxCost int32, inlMap map[*ir.Func]bool, edit func(ir.Node) ir.Node) ir.Node {
	if n == nil {
		return n
	}

	switch n.Op() {
	case ir.ODEFER, ir.OGO:
		n := n.(*ir.GoDeferStmt)
		switch call := n.Call; call.Op() {
		case ir.OCALLFUNC, ir.OCALLMETH:
			call := call.(*ir.CallExpr)
			call.NoInline = true
		}

	// 在这里（或更早）执行这些操作，
	// 所以逃逸分析可以避免更多的堆。
	case ir.OCLOSURE:
		return n
	case ir.OCALLMETH:
		// 防止在使用checkptr时内联某些reflect.Value方法，
		// 即使在没有它的情况下编译包reflect（#35073）。
		n := n.(*ir.CallExpr)
		if s := ir.MethodExprName(n.X).Sym(); base.Debug.Checkptr != 0 && types.IsReflectPkg(s.Pkg) && (s.Name == "Value.UnsafeAddr" || s.Name == "Value.Pointer") {
			return n
		}
	}

	lno := ir.SetPos(n)

	ir.EditChildren(n, edit)

	if as := n; as.Op() == ir.OAS2FUNC {
		as := as.(*ir.AssignListStmt)
		if as.Rhs[0].Op() == ir.OINLCALL {
			as.Rhs = inlconv2list(as.Rhs[0].(*ir.InlinedCallExpr))
			as.SetOp(ir.OAS2)
			as.SetTypecheck(0)
			n = typecheck.Stmt(as)
		}
	}

	// 所有的树枝都已断开，现在是时候
	// transmogrify此节点本身，除非被
	// 此功能顶部的开关。
	switch n.Op() {
	case ir.OCALLFUNC, ir.OCALLMETH:
		n := n.(*ir.CallExpr)
		if n.NoInline {
			return n
		}
	}

	var call *ir.CallExpr
	switch n.Op() {
	case ir.OCALLFUNC:
		call = n.(*ir.CallExpr)
		if base.Flag.LowerM > 3 {
			fmt.Printf("%v:call to func %+v\n", ir.Line(n), call.X)
		}
		if ir.IsIntrinsicCall(call) {
			break
		}
		if fn := inlCallee(call.X); fn != nil && fn.Inl != nil {
			n = mkinlcall(call, fn, maxCost, inlMap, edit)
		}

	case ir.OCALLMETH:
		call = n.(*ir.CallExpr)
		if base.Flag.LowerM > 3 {
			fmt.Printf("%v:call to meth %v\n", ir.Line(n), call.X.(*ir.SelectorExpr).Sel)
		}

		// typecheck应该解析ODOTMETH->type，其nname指向实际函数。
		if call.X.Type() == nil {
			base.Fatalf("no function type for [%p] %+v\n", call.X, call.X)
		}

		n = mkinlcall(call, ir.MethodExprName(call.X).Func, maxCost, inlMap, edit)
	}

	base.Pos = lno

	if n.Op() == ir.OINLCALL {
		ic := n.(*ir.InlinedCallExpr)
		switch call.Use {
		default:
			ir.Dump("call", call)
			base.Fatalf("call missing use")
		case ir.CallUseExpr:
			n = inlconv2expr(ic)
		case ir.CallUseStmt:
			n = inlconv2stmt(ic)
		case ir.CallUseList:
			// 留给来电者转换
		}
	}

	return n
}

// inlCallee接受函数类型的表达式，并在名称上返回基础函数
// 它指的是静态已知的。否则，它返回nil。
func inlCallee(fn ir.Node) *ir.Func {
	fn = ir.StaticValue(fn)
	switch fn.Op() {
	case ir.OMETHEXPR:
		fn := fn.(*ir.SelectorExpr)
		n := ir.MethodExprName(fn)
		// 检查接收器类型是否与fn.X匹配。
		// TODO（mdempsky）：处理隐式解引用
		// 指针-接收器参数？
		if n == nil || !types.Identical(n.Type().Recv().Type, fn.X.Type()) {
			return nil
		}
		return n.Func
	case ir.ONAME:
		fn := fn.(*ir.Name)
		if fn.Class == ir.PFUNC {
			return fn.Func
		}
	case ir.OCLOSURE:
		fn := fn.(*ir.ClosureExpr)
		c := fn.Func
		CanInline(c)
		return c
	}
	return nil
}

func inlParam(t *types.Field, as ir.InitNode, inlvars map[*ir.Name]*ir.Name) ir.Node {
	if t.Nname == nil {
		return ir.BlankNode
	}
	n := t.Nname.(*ir.Name)
	if ir.IsBlank(n) {
		return ir.BlankNode
	}
	inlvar := inlvars[n]
	if inlvar == nil {
		base.Fatalf("missing inlvar for %v", n)
	}
	as.PtrInit().Append(ir.NewDecl(base.Pos, ir.ODCL, inlvar))
	inlvar.Name().Defn = as
	return inlvar
}

var inlgen int

// SSADumpInline使SSA后端有机会转储函数
// 生成用于调试编译器本身的输出时。
var SSADumpInline = func(*ir.Func) {}

// 如果n是调用节点（OCALLFUNC或OCALLMETH），而fn是
// 函数的内联体，返回一个可以替换n的OINLCALL节点。
// 返回节点的Ninit具有参数赋值，Nbody是
// 内联函数体和（列表，Rlist）包含（输入，输出）
// 参数。
// mkinlcall的结果必须分配回n，例如。
// n、 左=MKILLCALL（n.Left，fn，isddd）
func mkinlcall(n *ir.CallExpr, fn *ir.Func, maxCost int32, inlMap map[*ir.Func]bool, edit func(ir.Node) ir.Node) ir.Node {
	if fn.Inl == nil {
		if logopt.Enabled() {
			logopt.LogOpt(n.Pos(), "cannotInlineCall", "inline", ir.FuncName(ir.CurFunc),
				fmt.Sprintf("%s cannot be inlined", ir.PkgFuncName(fn)))
		}
		return n
	}
	if fn.Inl.Cost > maxCost {
		// 内联函数体太大。通常，我们使用此检查来限制
		// 内联到非常大的函数中。见第26546和17566期。
		if logopt.Enabled() {
			logopt.LogOpt(n.Pos(), "cannotInlineCall", "inline", ir.FuncName(ir.CurFunc),
				fmt.Sprintf("cost %d of %s exceeds max large caller cost %d", fn.Inl.Cost, ir.PkgFuncName(fn), maxCost))
		}
		return n
	}

	if fn == ir.CurFunc {
		// 不能递归地将函数内联到自身中。
		if logopt.Enabled() {
			logopt.LogOpt(n.Pos(), "cannotInlineCall", "inline", fmt.Sprintf("recursive call to %s", ir.FuncName(ir.CurFunc)))
		}
		return n
	}

	if base.Flag.Cfg.Instrumenting && types.IsRuntimePkg(fn.Sym().Pkg) {
		// 不得检测运行时包。
		// 仪器跳过运行时包。但是，某些运行时代码可以是
		// 内联到其他包中并在其中插入指令。为了避免这种情况,，
		// 我们在检测时禁用运行时函数的内联。
		// 我们观察到的例子是LockOSThread的内联，
		// 这会导致m内容上的错误比赛报告。
		return n
	}

	if inlMap[fn] {
		if base.Flag.LowerM > 1 {
			fmt.Printf("%v: cannot inline %v into %v: repeated recursive cycle\n", ir.Line(n), fn, ir.FuncName(ir.CurFunc))
		}
		return n
	}
	inlMap[fn] = true
	defer func() {
		inlMap[fn] = false
	}()
	if base.Debug.TypecheckInl == 0 {
		typecheck.ImportedBody(fn)
	}

	// 我们有一个函数节点，它有一个内联体。
	if base.Flag.LowerM > 1 {
		fmt.Printf("%v: inlining call to %v %v { %v }\n", ir.Line(n), fn.Sym(), fn.Type(), ir.Nodes(fn.Inl.Body))
	} else if base.Flag.LowerM != 0 {
		fmt.Printf("%v: inlining call to %v\n", ir.Line(n), fn)
	}
	if base.Flag.LowerM > 2 {
		fmt.Printf("%v: Before inlining: %+v\n", ir.Line(n), n)
	}

	SSADumpInline(fn)

	ninit := n.Init()

	// 对于普通函数调用，函数被调用方表达式
	// 可能包含副作用（例如，addinit在
	// inlconv2expr或inlconv2list）。一定要保存好这些，
	// 如有必要（#42703）。
	if n.Op() == ir.OCALLFUNC {
		callee := n.X
		for callee.Op() == ir.OCONVNOP {
			conv := callee.(*ir.ConvExpr)
			ninit.Append(ir.TakeInit(conv)...)
			callee = conv.X
		}
		if callee.Op() != ir.ONAME && callee.Op() != ir.OCLOSURE && callee.Op() != ir.OMETHEXPR {
			base.Fatalf("unexpected callee expression: %v", callee)
		}
	}

	// 使用临时名称代替原始名称。
	inlvars := make(map[*ir.Name]*ir.Name)

	// 记录正式文件/本地文件，以备日后处理
	var inlfvars []*ir.Name

	for _, ln := range fn.Inl.Dcl {
		if ln.Op() != ir.ONAME {
			continue
		}
		if ln.Class == ir.PPARAMOUT { // 返回值处理如下。
			continue
		}
		inlf := typecheck.Expr(inlvar(ln)).(*ir.Name)
		inlvars[ln] = inlf
		if base.Flag.GenDwarfInl > 0 {
			if ln.Class == ir.PPARAM {
				inlf.Name().SetInlFormal(true)
			} else {
				inlf.Name().SetInlLocal(true)
			}
			inlf.SetPos(ln.Pos())
			inlfvars = append(inlfvars, inlf)
		}
	}

	// 如果出现以下情况，我们可以延迟声明+初始化结果参数：
	// （1） 内联函数中只有一个“return”语句；
	// （2） 它不是一个空的返回语句（#44355）；和
	// （3） 结果参数未命名。
	delayretvars := true

	nreturns := 0
	ir.VisitList(ir.Nodes(fn.Inl.Body), func(n ir.Node) {
		if n, ok := n.(*ir.ReturnStmt); ok {
			nreturns++
			if len(n.Results) == 0 {
				delayretvars = false // 空返回语句（案例2）
			}
		}
	})

	if nreturns != 1 {
		delayretvars = false // 不完全是一个返回语句（案例1）
	}

	// 返回值的临时值。
	var retvars []ir.Node
	for i, t := range fn.Type().Results().Fields().Slice() {
		var m *ir.Name
		if nn := t.Nname; nn != nil && !ir.IsBlank(nn.(*ir.Name)) && !strings.HasPrefix(nn.Sym().Name, "~r") {
			n := nn.(*ir.Name)
			m = inlvar(n)
			m = typecheck.Expr(m).(*ir.Name)
			inlvars[n] = m
			delayretvars = false // 找到一个命名的结果参数（案例3）
		} else {
			// 匿名返回值，合成用于替换返回值的赋值的名称
			m = retvar(t, i)
		}

		if base.Flag.GenDwarfInl > 0 {
			// 如果返回变量的src.Pos
			// 是由内胆制造的（例如“~R2”）；这样的变形金刚
			// 不是原始被调用方的一部分。
			if !strings.HasPrefix(m.Sym().Name, "~R") {
				m.Name().SetInlFormal(true)
				m.SetPos(t.Pos)
				inlfvars = append(inlfvars, m)
			}
		}

		retvars = append(retvars, m)
	}

	// 为参数的临时名称指定参数。
	as := ir.NewAssignListStmt(base.Pos, ir.OAS2, nil, nil)
	as.Def = true
	if n.Op() == ir.OCALLMETH {
		sel := n.X.(*ir.SelectorExpr)
		if sel.X == nil {
			base.Fatalf("method call without receiver: %+v", n)
		}
		as.Rhs.Append(sel.X)
	}
	as.Rhs.Append(n.Args...)

	// 对于变量函数的非点调用，我们指定
	// 变量参数的临时名称。
	var vas *ir.AssignStmt

	if recv := fn.Type().Recv(); recv != nil {
		as.Lhs.Append(inlParam(recv, as, inlvars))
	}
	for _, param := range fn.Type().Params().Fields().Slice() {
		// 对于中的普通参数或可变参数
		// 虚线调用，只需将变量添加到
		// 任务清单，我们完成了。
		if !param.IsDDD() || n.IsDDD {
			as.Lhs.Append(inlParam(param, as, inlvars))
			continue
		}

		// 否则，我们需要收集剩余的值
		// 作为一片通过。

		x := len(as.Lhs)
		for len(as.Lhs) < len(as.Rhs) {
			as.Lhs.Append(argvar(param.Type, len(as.Lhs)))
		}
		varargs := as.Lhs[x:]

		vas = ir.NewAssignStmt(base.Pos, nil, nil)
		vas.X = inlParam(param, vas, inlvars)
		if len(varargs) == 0 {
			vas.Y = typecheck.NodNil()
			vas.Y.SetType(param.Type)
		} else {
			lit := ir.NewCompLitExpr(base.Pos, ir.OCOMPLIT, ir.TypeNode(param.Type), nil)
			lit.List = varargs
			vas.Y = lit
		}
	}

	if len(as.Rhs) != 0 {
		ninit.Append(typecheck.Stmt(as))
	}

	if vas != nil {
		ninit.Append(typecheck.Stmt(vas))
	}

	if !delayretvars {
		// 将返回参数归零。
		for _, n := range retvars {
			ninit.Append(ir.NewDecl(base.Pos, ir.ODCL, n.(*ir.Name)))
			ras := ir.NewAssignStmt(base.Pos, n, nil)
			ninit.Append(typecheck.Stmt(ras))
		}
	}

	retlabel := typecheck.AutoLabel(".i")

	inlgen++

	parent := -1
	if b := base.Ctxt.PosTable.Pos(n.Pos()).Base(); b != nil {
		parent = b.InliningIndex()
	}

	sym := fn.Linksym()
	newIndex := base.Ctxt.InlTree.Add(parent, n.Pos(), sym)

	// 在内联主体之前添加内联标记。
	// 这个标记在代码中是内联的，因此它是一个合理的位置
	// 设置断点。不确定这是否真的有必要
	// （在这种情况下，可以改为在函数末尾执行）。
	// 注：第28603号问题。
	inlMark := ir.NewInlineMarkStmt(base.Pos, types.BADWIDTH)
	inlMark.SetPos(n.Pos().WithIsStmt())
	inlMark.Index = int64(newIndex)
	ninit.Append(inlMark)

	if base.Flag.GenDwarfInl > 0 {
		if !sym.WasInlined() {
			base.Ctxt.DwFixups.SetPrecursorFunc(sym, fn)
			sym.Set(obj.AttrWasInlined, true)
		}
	}

	subst := inlsubst{
		retlabel:     retlabel,
		retvars:      retvars,
		delayretvars: delayretvars,
		inlvars:      inlvars,
		defnMarker:   ir.NilExpr{},
		bases:        make(map[*src.PosBase]*src.PosBase),
		newInlIndex:  newIndex,
		fn:           fn,
	}
	subst.edit = subst.node

	body := subst.list(ir.Nodes(fn.Inl.Body))

	lab := ir.NewLabelStmt(base.Pos, retlabel)
	body = append(body, lab)

	if !typecheck.Go117ExportTypes {
		typecheck.Stmts(body)
	}

	if base.Flag.GenDwarfInl > 0 {
		for _, v := range inlfvars {
			v.SetPos(subst.updatedPos(v.Pos()))
		}
	}

	// 转储列表（“ninit post”，ninit）；

	call := ir.NewInlinedCallExpr(base.Pos, nil, nil)
	*call.PtrInit() = ninit
	call.Body = body
	call.ReturnVars = retvars
	call.SetType(n.Type())
	call.SetTypecheck(1)

	// 传递内联
	// 在导出实体之前执行此操作可能会更好，
	// 但无法发射内联已展开的实体。
	// 相反，我们会释放身体需要的东西
	// 并且每次使用都必须重做内联。
	// 幸运的是这些都很小。
	ir.EditChildren(call, edit)

	if base.Flag.LowerM > 2 {
		fmt.Printf("%v: After inlining %+v\n\n", ir.Line(call), call)
	}

	return call
}

// 每次我们展开一个函数，就会生成一组新的tmpname，
// PAUTO在调用函数中，并将它们与
// 被调用函数的PPARAM、PAUTOS和PPARAMOUTs。
func inlvar(var_ *ir.Name) *ir.Name {
	if base.Flag.LowerM > 3 {
		fmt.Printf("inlvar %+v\n", var_)
	}

	n := typecheck.NewName(var_.Sym())
	n.SetType(var_.Type())
	n.Class = ir.PAUTO
	n.SetUsed(true)
	n.Curfn = ir.CurFunc // 调用函数，而不是被调用函数
	n.SetAddrtaken(var_.Addrtaken())

	ir.CurFunc.Dcl = append(ir.CurFunc.Dcl, n)
	return n
}

// 合成一个变量以存储内联函数的结果。
func retvar(t *types.Field, i int) *ir.Name {
	n := typecheck.NewName(typecheck.LookupNum("~R", i))
	n.SetType(t.Type)
	n.Class = ir.PAUTO
	n.SetUsed(true)
	n.Curfn = ir.CurFunc // 调用函数，而不是被调用函数
	ir.CurFunc.Dcl = append(ir.CurFunc.Dcl, n)
	return n
}

// 合成一个变量以存储内联函数的参数
// 当他们来自一个多回音呼叫时。
func argvar(t *types.Type, i int) ir.Node {
	n := typecheck.NewName(typecheck.LookupNum("~arg", i))
	n.SetType(t.Elem())
	n.Class = ir.PAUTO
	n.SetUsed(true)
	n.Curfn = ir.CurFunc // 调用函数，而不是被调用函数
	ir.CurFunc.Dcl = append(ir.CurFunc.Dcl, n)
	return n
}

// inlsubst类型实现单个
// 函数调用。
type inlsubst struct {
	// 后藤的目标被替换为回报。
	retlabel *types.Sym

	// 临时结果变量。
	retvars []ir.Node

	// 结果变量是否应在
	// “return”语句。
	delayretvars bool

	inlvars map[*ir.Name]*ir.Name
	// defnMarker用于标记要重新分配的节点。
	// inlsubst.clovar在创建新ONAME时设置此选项。
	// inlsubst.node将为inlvar设置正确的Defn。
	defnMarker ir.NilExpr

	// bases使用额外的
	// 内联调用帧。
	bases map[*src.PosBase]*src.PosBase

	// newInlIndex是指向的内联调用帧的索引
	// 为内联节点插入。
	newInlIndex int

	edit func(ir.Node) ir.Node // subst.node方法值闭包的缓存副本

	// 如果非nil，则在内联函数的闭包内，并且
	// newclofn是新内联闭包的函数。
	newclofn *ir.Func

	fn *ir.Func // For debug——正在内联的func

	// 如果为true，则在替换期间不更新源位置
	// （保留旧的震源位置）。
	noPosUpdate bool
}

// 列表内联节点列表。
func (subst *inlsubst) list(ll ir.Nodes) []ir.Node {
	s := make([]ir.Node, 0, len(ll))
	for _, n := range ll {
		s = append(s, subst.node(n))
	}
	return s
}

// fields返回表示接收方的结构类型的字段列表，
// 复制字段节点并替换
// 字段节点中的名称节点。
func (subst *inlsubst) fields(oldt *types.Type) []*types.Field {
	oldfields := oldt.FieldSlice()
	newfields := make([]*types.Field, len(oldfields))
	for i := range oldfields {
		newfields[i] = oldfields[i].Copy()
		if oldfields[i].Nname != nil {
			newfields[i].Nname = subst.node(oldfields[i].Nname.(*ir.Name))
		}
	}
	return newfields
}

// clovar为闭包的局部变量或参数创建新的ONAME节点
// 在内联的函数中。
func (subst *inlsubst) clovar(n *ir.Name) *ir.Name {
	// TODO（danscales）：想要摆脱这个浅拷贝，代码如下
	// 但很难以可维护的方式复制所有必要的标志。
	// m:=ir.NewNameAt（n.Pos（），n.Sym（））
	// m、 阶级
	// m、 SetType（n.Type（））
	// m、 设置类型检查（1）
	// 如果n.IsClosureVar（）{
	// m、 SetIsClosureVar（真）
	// }
	m := &ir.Name{}
	*m = *n
	m.Curfn = subst.newclofn

	switch defn := n.Defn.(type) {
	case nil:
		// 好啊
	case *ir.Name:
		if !n.IsClosureVar() {
			base.FatalfAt(n.Pos(), "want closure variable, got: %+v", n)
		}
		if n.Sym().Pkg != types.LocalPkg {
			// 如果闭包来自函数的内联
			// 另一个包，必须更改捕获的包
			// 变量设置为localpkg，以便
			// struct是本地包，即使在
			// 不导出名称。如果禁用此代码，则可以
			// 通过运行“go测试”重现问题
			// go/internal/SRC导入器'。TODO（mdempsky）-可能会改变
			// 我们如何创建闭包结构？
			m.SetSym(types.LocalPkg.Lookup(n.Sym().Name))
		}
		// 确保任何inlvar都是Defn
		// 重写ONAME闭包变量的
		// 在内联过程中。不要代替
		// 如果Defn节点在内联函数之外。
		if subst.inlvars[n.Defn.(*ir.Name)] != nil {
			m.Defn = subst.node(n.Defn)
		}
	case *ir.AssignStmt, *ir.AssignListStmt:
		// 在inlsubst.node末尾标记要重新分配的节点。
		m.Defn = &subst.defnMarker
	case *ir.TypeSwitchGuard:
		// TODO（mdempsky）：正确设置m.Defn。见关于#45743的讨论。
	default:
		base.FatalfAt(n.Pos(), "unexpected Defn: %+v", defn)
	}

	if n.Outer != nil {
		// 外部变量在内联函数中定义，
		// 我们将用替换变量替换它，或者
		// 在内联函数外部定义，我们应该
		// 跳过外部变量（函数的闭包变量
		// 正在内联）。
		s := subst.node(n.Outer).(*ir.Name)
		if s == n.Outer {
			s = n.Outer.Outer
		}
		m.Outer = s
	}
	return m
}

// closure为ClosureExpr n执行必要的子条件，并返回新的
// 闭包节点。
func (subst *inlsubst) closure(n *ir.ClosureExpr) ir.Node {
	m := ir.Copy(n)

	// 在编辑subst之前，将INLSUBT中的标志设置为
	// 表示我们不想更新中的源位置
	// 新的关闭。如果我们这样做的话，似乎关闭
	// 它本身就有内在的东西，但事实并非如此。看见
	// 有关更多详细信息，请参阅第46234期。
	defer func(prev bool) { subst.noPosUpdate = prev }(subst.noPosUpdate)
	subst.noPosUpdate = true
	ir.EditChildren(m, subst.edit)

	// fmt.Printf（“将函数%v和闭包内联到%v\n”，subst.fn，ir.FuncName（ir.CurFunc））

	// 以下内容与funcLit类似
	oldfn := n.Func
	newfn := ir.NewFunc(oldfn.Pos())
	// 这三条线并不是绝对必要的，只是为了清楚起见
	// 这个新函数需要重新进行类型检查和导入。
	newfn.SetTypecheck(0)
	newfn.SetInlinabilityChecked(false)
	newfn.Inl = nil
	newfn.SetIsHiddenClosure(true)
	newfn.Nname = ir.NewNameAt(n.Pos(), ir.BlankNode.Sym())
	newfn.Nname.Func = newfn
	// 对于-G=3模式，Ntype可以为零。
	if oldfn.Nname.Ntype != nil {
		newfn.Nname.Ntype = subst.node(oldfn.Nname.Ntype).(ir.Ntype)
	}
	newfn.Nname.Defn = newfn

	m.(*ir.ClosureExpr).Func = newfn
	newfn.OClosure = m.(*ir.ClosureExpr)

	if subst.newclofn != nil {
		// fmt.Printf（“用嵌套闭包内联闭包\n”）
	}
	prevxfunc := subst.newclofn

	// 标记我们现在在闭包中替换（在
	// 内联函数），并为所有本地
	// 此闭包中的变量/参数。
	subst.newclofn = newfn
	newfn.Dcl = nil
	newfn.ClosureVars = nil
	for _, oldv := range oldfn.Dcl {
		newv := subst.clovar(oldv)
		subst.inlvars[oldv] = newv
		newfn.Dcl = append(newfn.Dcl, newv)
	}
	for _, oldv := range oldfn.ClosureVars {
		newv := subst.clovar(oldv)
		subst.inlvars[oldv] = newv
		newfn.ClosureVars = append(newfn.ClosureVars, newv)
	}

	// 需要替换中的ONAME节点
	// newfn.Type（）.FuncType（）.Receiver/Params/Results.FieldSlice（）.Nname
	oldt := oldfn.Type()
	newrecvs := subst.fields(oldt.Recvs())
	var newrecv *types.Field
	if len(newrecvs) > 0 {
		newrecv = newrecvs[0]
	}
	newt := types.NewSignature(oldt.Pkg(), newrecv,
		nil, subst.fields(oldt.Params()), subst.fields(oldt.Results()))

	newfn.Nname.SetType(newt)
	newfn.Body = subst.list(oldfn.Body)

	// 从subst.inlvars中删除当前闭包的节点
	for _, oldv := range oldfn.Dcl {
		delete(subst.inlvars, oldv)
	}
	for _, oldv := range oldfn.ClosureVars {
		delete(subst.inlvars, oldv)
	}
	// 返回上一个闭包函数
	subst.newclofn = prevxfunc

	// 实际上为闭包创建命名函数，现在
	// 闭包内联在特定函数中。
	m.SetTypecheck(0)
	if oldfn.ClosureCalled() {
		typecheck.Callee(m)
	} else {
		typecheck.Expr(m)
	}
	return m
}

// 节点从已保存的原始实体中递归复制节点
// 内联函数，替换对输入/输出的引用
// 参数，并将返回值替换为
// 对输出的赋值。
func (subst *inlsubst) node(n ir.Node) ir.Node {
	if n == nil {
		return nil
	}

	switch n.Op() {
	case ir.ONAME:
		n := n.(*ir.Name)

		// 内联闭包时处理捕获的变量。
		if n.IsClosureVar() && subst.newclofn == nil {
			o := n.Outer

			// 处理闭包序列内联的情况。
			// TODO（danscales）-编写测试用例以查看是否需要
			// 上多个层次。
			if o.Curfn != ir.CurFunc {
				o = o.Outer
			}

			// 确保外部参数与内联位置匹配
			if o == nil || o.Curfn != ir.CurFunc {
				base.Fatalf("%v: unresolvable capture %v\n", ir.Line(n), n)
			}

			if base.Flag.LowerM > 2 {
				fmt.Printf("substituting captured name %+v  ->  %+v\n", n, o)
			}
			return o
		}

		if inlvar := subst.inlvars[n]; inlvar != nil { // 这些将在导入节点期间设置
			if base.Flag.LowerM > 2 {
				fmt.Printf("substituting name %+v  ->  %+v\n", n, inlvar)
			}
			return inlvar
		}

		if base.Flag.LowerM > 2 {
			fmt.Printf("not substituting name %+v\n", n)
		}
		return n

	case ir.OMETHEXPR:
		n := n.(*ir.SelectorExpr)
		return n

	case ir.OLITERAL, ir.ONIL, ir.OTYPE:
		// 如果n是命名常量或类型，则可以继续
		// 在内联副本中使用它。否则，复制一份
		// 所以我们可以更新行号。
		if n.Sym() != nil {
			return n
		}

	case ir.ORETURN:
		if subst.newclofn != nil {
			// 如果在闭包内，不要进行特殊替换
			break
		}
		// 因为我们不会用闭包来处理尸体，
		// 此返回保证属于当前内联函数。
		n := n.(*ir.ReturnStmt)
		init := subst.list(n.Init())
		if len(subst.retvars) != 0 && len(n.Results) != 0 {
			as := ir.NewAssignListStmt(base.Pos, ir.OAS2, nil, nil)

			// 制作一个浅显的retvars副本。
			// 否则，OINLCALL.Rlist将是相同的列表，
			// 而稍后的步行和打字检查可能会把它击垮。
			for _, n := range subst.retvars {
				as.Lhs.Append(n)
			}
			as.Rhs = subst.list(n.Results)

			if subst.delayretvars {
				for _, n := range as.Lhs {
					as.PtrInit().Append(ir.NewDecl(base.Pos, ir.ODCL, n.(*ir.Name)))
					n.Name().Defn = as
				}
			}

			init = append(init, typecheck.Stmt(as))
		}
		init = append(init, ir.NewBranchStmt(base.Pos, ir.OGOTO, subst.retlabel))
		typecheck.Stmts(init)
		return ir.NewBlockStmt(base.Pos, init)

	case ir.OGOTO:
		if subst.newclofn != nil {
			// 如果在闭包内，不要进行特殊替换
			break
		}
		n := n.(*ir.BranchStmt)
		m := ir.Copy(n).(*ir.BranchStmt)
		m.SetPos(subst.updatedPos(m.Pos()))
		*m.PtrInit() = nil
		p := fmt.Sprintf("%s·%d", n.Label.Name, inlgen)
		m.Label = typecheck.Lookup(p)
		return m

	case ir.OLABEL:
		if subst.newclofn != nil {
			// 如果在闭包内，不要进行特殊替换
			break
		}
		n := n.(*ir.LabelStmt)
		m := ir.Copy(n).(*ir.LabelStmt)
		m.SetPos(subst.updatedPos(m.Pos()))
		*m.PtrInit() = nil
		p := fmt.Sprintf("%s·%d", n.Label.Name, inlgen)
		m.Label = typecheck.Lookup(p)
		return m

	case ir.OCLOSURE:
		return subst.closure(n.(*ir.ClosureExpr))

	}

	m := ir.Copy(n)
	m.SetPos(subst.updatedPos(m.Pos()))
	ir.EditChildren(m, subst.edit)

	switch m := m.(type) {
	case *ir.AssignStmt:
		if lhs, ok := m.X.(*ir.Name); ok && lhs.Defn == &subst.defnMarker {
			lhs.Defn = m
		}
	case *ir.AssignListStmt:
		for _, lhs := range m.Lhs {
			if lhs, ok := lhs.(*ir.Name); ok && lhs.Defn == &subst.defnMarker {
				lhs.Defn = m
			}
		}
	}

	return m
}

func (subst *inlsubst) updatedPos(xpos src.XPos) src.XPos {
	if subst.noPosUpdate {
		return xpos
	}
	pos := base.Ctxt.PosTable.Pos(xpos)
	oldbase := pos.Base() // 可以是零
	newbase := subst.bases[oldbase]
	if newbase == nil {
		newbase = src.NewInliningBase(oldbase, subst.newInlIndex)
		subst.bases[oldbase] = newbase
	}
	pos.SetBase(newbase)
	return base.Ctxt.PosTable.XPos(pos)
}

func pruneUnusedAutos(ll []*ir.Name, vis *hairyVisitor) []*ir.Name {
	s := make([]*ir.Name, 0, len(ll))
	for _, n := range ll {
		if n.Class == ir.PAUTO {
			if !vis.usedLocals.Has(n) {
				continue
			}
		}
		s = append(s, n)
	}
	return s
}

// numNonClosures返回列表中不是闭包的函数数。
func numNonClosures(list []*ir.Func) int {
	count := 0
	for _, fn := range list {
		if fn.OClosure == nil {
			count++
		}
	}
	return count
}

func doList(list []ir.Node, do func(ir.Node) bool) bool {
	for _, x := range list {
		if x != nil {
			if do(x) {
				return true
			}
		}
	}
	return false
}
